use minio::s3::client::Client;
use minio::s3::creds::StaticProvider;
use minio::s3::http::BaseUrl;
use minio::s3::types::{S3Api, ToS3Request};
// use minio::s3::args::GetPresignedObjectUrlArgs;
use crate::app_error::AppError;
// use hyper::http::Method;
// use minio::s3::http
use reqwest::Method;
use minio::s3::args::*;
use minio::s3::builders::RemoveObject;
use crate::config::AppConfig;

#[derive(Clone)]
pub struct MinioClientState {
    client: Client,
    bucket_name: String,
}

impl MinioClientState {
    pub async fn get_presigned_object_download_url(
        &self,
        object_name: &str,
    ) -> Result<String, AppError> {
        let mut args =
            GetPresignedObjectUrlArgs::new(self.bucket_name.as_str(), object_name, Method::GET)?;
        args.expiry_seconds = Some(60 * 30); // 0.5 hour
        let url = self.client.get_presigned_object_url(&args).await?.url;
        Ok(url)
    }

    pub async fn get_presigned_object_upload_url(&self, object_name: &str) -> Result<String, AppError>  {
        let mut args =
            GetPresignedObjectUrlArgs::new(self.bucket_name.as_str(), object_name, Method::PUT)?;
        args.expiry_seconds = Some(60 * 30); // 0.5 hour
        let url =self.client
            .get_presigned_object_url(&args)
            .await?
            .url;
        Ok(url)
    }

    pub async fn delete_object(&self, object_name: &str) -> Result<(), AppError> {
        self.client.remove_object(self.bucket_name.as_str(), object_name).send().await?;
        Ok(())
    }

    pub async fn exists_object(&self, object_name: &str) -> Result<(), AppError> {
        let args = StatObjectArgs::new(self.bucket_name.as_str(), object_name).unwrap();
        self.client.stat_object(&args).await?;
        Ok(())
    }

    // https://github.com/minio/minio-rs/issues/66
    pub async fn put_object(&self, object_name: &str, data: &[u8]) -> Result<(), AppError> {
        let put_args = PutObjectApiArgs::new(&self.bucket_name.as_str(), object_name, data).unwrap();
        
        self.client.put_object_api(&put_args).await?;
        Ok(())
    }
}

pub async fn get_minio_client(app_config:&AppConfig) -> MinioClientState {
    let ep: BaseUrl = app_config.minio_endpoint.parse().unwrap();
    let static_provider = StaticProvider::new(&app_config.minio_access_key, &app_config.minio_secret_key, None);
    let cli = Client::new(ep, Some(Box::new(static_provider)), None, None)
        .expect("Failed to create minio client");
    MinioClientState {
        client: cli,
        bucket_name: app_config.minio_bucket.clone(),
    }
}

// 测试
#[cfg(test)]
mod tests {
    use super::*;

    #[tokio::test]
    async fn test_minio_client() {
        let minio_endpoint = std::env::var("MINIO_ENDPOINT").expect("MINIO_ENDPOINT not found");
        let minio_access_key =
            std::env::var("MINIO_ACCESS_KEY").expect("MINIO_ACCESS_KEY not found");
        let minio_secret_key =
            std::env::var("MINIO_SECRET_KEY").expect("MINIO_SECRET_KEY not found");
        let minio_bucket = std::env::var("MINIO_BUCKET_NAME").expect("MINIO_BUCKET not found");

        let base_url: BaseUrl = minio_endpoint.parse().unwrap();
        let static_provider = StaticProvider::new(&minio_access_key, &minio_secret_key, None);
        let minio_client = Client::new(base_url, Some(Box::new(static_provider)), None, None)
            .expect("Minio client creation failed");

        // let minio_client = MinioClient {};
        // assert_eq!(minio_client, MinioClient {});
        let arg = BucketExistsArgs::new(minio_bucket.as_str()).unwrap();
        let exist = minio_client
            .bucket_exists(&arg)
            .await
            .expect("Bucket exists failed");

        //    let put_arg = PutObjectArgs::new(minio_bucket.as_str(), "test.txt", "hello world".as_bytes().to_vec()).unwrap();
        let put_args = PutObjectApiArgs::new(
            minio_bucket.as_str(),
            "test.txt",
            "hello world 2".as_bytes(),
        )
        .unwrap();
        minio_client.put_object_api(&put_args).await.unwrap();
        assert!(exist);

        // http::Method::GET
        let mut args =
            GetPresignedObjectUrlArgs::new(minio_bucket.as_str(), "test.txt", Method::GET).unwrap();
        args.expiry_seconds = Some(60 * 60); // 1 hour
        let url = minio_client.get_presigned_object_url(&args).await.unwrap();

        println!("Presigned URL: {}", url.url);

        let mut args =
            GetPresignedObjectUrlArgs::new(minio_bucket.as_str(), "test2.m4a", Method::PUT)
                .unwrap();
        args.expiry_seconds = Some(60 * 60); // 1 hour
        let url = minio_client.get_presigned_object_url(&args).await.unwrap();
        println!("Presigned URL post: {}", url.url);
    }
}
